//
//  Copyright 2011 Alexei "Oleksiy" Puzikov. All rights reserved.
//

//  Cocoa implementation of Three Trackballs user-space driver

#import "tb_controller.h"
#import "DDHidMouse.h"

const int trackBallVendorId = 0x47D; // Kensington/Primax
const int trackBallProductId = 0x2048; // Orbit

CGPoint currentMousePosition;

int trackBallCursorIndex = -1;
int trackBallButtonIndex = -1;
int trackBallWheelIndex = -1;

int trackballDx[3] = { 0, 0, 0 };
int trackballDy[3] = { 0, 0, 0 };
int trackballWheel[3] = { 0, 0, 0 };

#define VERIFY_TRACKBALL_INDEX(tball, ret) if ((tball<0) || (tball>2)) return ret;
#define VERIFY_TRACKBALL_INDEX_NORET(tball) if ((tball<0) || (tball>2)) return;

@implementation tb_controller

- (id)init
{
    self = [super init];
    if (self) {
		
		NSArray * mice = [DDHidMouse allMice];
		[mice makeObjectsPerformSelector: @selector(setDelegate:) withObject: self];
		
		[self setMice: mice];	
		
		NSPredicate *newPredicate = [NSPredicate predicateWithFormat:@"vendorId == %d", trackBallVendorId];
		
		NSArray *filtered = [mice filteredArrayUsingPredicate:newPredicate];
				
		[self setTrackballs:filtered];
				
	//	CGSetLocalEventsSuppressionInterval(0.0);		
		
    }
    return self;
}

#pragma mark Public API

- (void) startListening {
	int i;
	for (i=0; i < [[self mice] count]; i++) {
		[[[self mice] objectAtIndex:i] startListening];
	}
}

- (void) stopListening {
	int i;
	for (i=0; i < [[self mice] count]; i++) {
		[[[self mice] objectAtIndex:i] stopListening];
	}	
}

- (int) getTrackballCursorIndex {	
	return trackBallCursorIndex;
}

- (int) getTrackballButtonIndex {	
	return trackBallButtonIndex;
}

- (int) getTrackballWheelIndex {	
	return trackBallWheelIndex;
}


- (int) getTrackballDx: (int) tball {

	VERIFY_TRACKBALL_INDEX(tball, 0);
	return trackballDx[tball];
}

- (int) getTrackballDy: (int) tball {
	
	VERIFY_TRACKBALL_INDEX(tball, 0);
	return trackballDy[tball];
}

- (void) resetTrackball: (int) tball {

	[self resetTrackballCoordinates:tball];
	[self resetTrackballWheel:tball];

}

- (void) resetTrackballCoordinates: (int) tball {

	VERIFY_TRACKBALL_INDEX_NORET(tball);
	trackballDx[tball] = 0;
	trackballDy[tball] = 0;

}

- (void) resetTrackballWheel: (int) tball {

    VERIFY_TRACKBALL_INDEX_NORET(tball);
	trackballWheel[tball] = 0;

}

- (void) setTrackballDx: (int) tball: (int) dx {

    VERIFY_TRACKBALL_INDEX_NORET(tball);
	trackballDx[tball] = dx;

}

- (void) setTrackballDy: (int) tball: (int) dy {

	VERIFY_TRACKBALL_INDEX_NORET(tball);
	trackballDy[tball] = 0;

}

- (int) getTrackballWheel: (int) tball {

	VERIFY_TRACKBALL_INDEX(tball, 0);
	return trackballWheel[tball];

}

- (void) setTrackballWheel: (int) tball: (int) dw {

	VERIFY_TRACKBALL_INDEX_NORET(tball);
	trackballWheel[tball] = dw;

}

#pragma mark Implementation details

- (NSArray *) mice
{
    return mMice; 
}

- (NSArray *) trackBalls
{
    return mTrackballs; 
}


- (void) setMice: (NSArray *) theMice
{
    if (mMice != theMice)
    {
        [mMice release];
        mMice = [theMice retain];
    }
}

- (void) setTrackballs: (NSArray *) theTrackballs
{
    if (mTrackballs != theTrackballs)
    {
        [mTrackballs release];
        mTrackballs = [theTrackballs retain];
    }
}

#pragma mark Methods being called from delegate callbacks

- (void) mouseCoordinatesTracker:(DDHidMouse *) mouse withDx:(int) dx andDy:(int) dy {
	
	long index = [[self trackBalls] indexOfObject:mouse];
	
	if (index == NSNotFound) {
		
		CGEventRef ourEvent = CGEventCreate(NULL);
		currentMousePosition = CGEventGetLocation(ourEvent);
		CFRelease(ourEvent);
		
		trackBallCursorIndex = -1;
		
	}
	else {
		
		CGWarpMouseCursorPosition(currentMousePosition);
		trackBallCursorIndex = index;
		
		VERIFY_TRACKBALL_INDEX_NORET(index);
		trackballDx[trackBallCursorIndex] += dx;
		trackballDy[trackBallCursorIndex] += dy;

	}	
	
}

- (void) mouseButtonsTracker:(DDHidMouse *) mouse {
	long index = [[self trackBalls] indexOfObject:mouse];
	
	if (index == NSNotFound) {
		trackBallButtonIndex = -1;
	}
	else {
		trackBallButtonIndex = index;
	}	
}

- (void) mouseWheelTracker:(DDHidMouse *) mouse withDx:(int) dx; {
	
	long index = [[self trackBalls] indexOfObject:mouse];
	
	if (index == NSNotFound) {
		trackBallWheelIndex = -1;
	}
	else {
		trackBallWheelIndex = index;
		VERIFY_TRACKBALL_INDEX_NORET(index);		
		trackballWheel[trackBallWheelIndex] += dx;
	}		
}

#pragma mark Delegate callbacks

- (void) ddhidMouse: (DDHidMouse *) mouse xChanged: (SInt32) deltaX;
{
	[self mouseCoordinatesTracker:mouse withDx:deltaX andDy:0];
}

- (void) ddhidMouse: (DDHidMouse *) mouse yChanged: (SInt32) deltaY;
{
	[self mouseCoordinatesTracker:mouse withDx:0 andDy:deltaY];	
}

- (void) ddhidMouse: (DDHidMouse *) mouse wheelChanged: (SInt32) deltaWheel;
{
	[self mouseWheelTracker:mouse withDx:deltaWheel];	
}

- (void) ddhidMouse: (DDHidMouse *) mouse buttonDown: (unsigned) buttonNumber;
{
	[self mouseButtonsTracker:mouse];
}

- (void) ddhidMouse: (DDHidMouse *) mouse buttonUp: (unsigned) buttonNumber;
{
	[self mouseButtonsTracker:mouse];
}

@end
